{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Group Module"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Group module allows you to combine several elements of a pandapower net into a group. Various functions are available, which are then automatically applied to all elements in this group.\n",
    "This tutorial shows you how to create groups and how to use some helpful simple group functions.\n",
    "\n",
    "To analyse the group functionality we use the CIGRE MV net."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:11.821586Z",
     "start_time": "2025-10-20T06:20:08.151342Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from pandapower.networks import create_cigre_network_mv\n",
    "from pandapower.create import create_group_from_dict, create_group\n",
    "from pandapower.groups import (\n",
    "    set_group_in_service,\n",
    "    set_group_reference_column,\n",
    "    group_element_index,\n",
    "    set_value_to_group,\n",
    "    set_group_out_of_service,\n",
    "    group_res_q_mvar,\n",
    "    group_res_p_mw,\n",
    "    attach_to_group,\n",
    "    detach_from_group,\n",
    "    count_group_elements,\n",
    "    remove_not_existing_group_members,\n",
    "    ensure_lists_in_group_element_column\n",
    ")\n",
    "from pandapower.toolbox.grid_modification import get_connected_elements_dict\n",
    "from pandapower.run import runpp\n",
    "\n",
    "import warnings\n",
    "warnings.simplefilter(action='ignore', category=FutureWarning)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:12.083580Z",
     "start_time": "2025-10-20T06:20:11.827388Z"
    }
   },
   "outputs": [],
   "source": [
    "net = create_cigre_network_mv(with_der=\"all\")\n",
    "net.switch[\"closed\"] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create Groups\n",
    "As examples, we define two groups, one to represent a virtual power plant with sgens, loads and a storage and for all elements of the second feeder of the net.\n",
    "You can create groups using lists of element types and element indices, or you can pass all that information in one dict."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:12.118933Z",
     "start_time": "2025-10-20T06:20:12.089672Z"
    }
   },
   "outputs": [],
   "source": [
    "# define a Group as virtual power plant\n",
    "gr1_name = \"virtual power plant\"\n",
    "vpp_element_types = [\"storage\", \"sgen\", \"load\"]\n",
    "vpp_elements = [[1], [6, 8, 9, 10, 11, 12], [5, 6]]\n",
    "gr1_idx = create_group(net, vpp_element_types, vpp_elements, name=gr1_name)\n",
    "\n",
    "# define a Group of a Feeder 2\n",
    "gr2_name = \"Feeder2\"\n",
    "feeder2buses = [12, 13, 14]\n",
    "feeder2_elements_dict = get_connected_elements_dict(net, feeder2buses)\n",
    "feeder2_elements_dict[\"bus\"] = feeder2buses\n",
    "gr2_idx = create_group_from_dict(net, feeder2_elements_dict, name=gr2_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can see that there are entries in `net.group` for both groups. As usual in pandapower, the information is accessed by means of the index. For groups with multiple element types, e.g. the virtual power plant includes storages, sgens and loads, multiple rows with the same index are created to `net.group`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:12.134858Z",
     "start_time": "2025-10-20T06:20:12.123744Z"
    }
   },
   "outputs": [],
   "source": [
    "print(net.group)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set `reference_column`\n",
    "However, the user can manipulate the indices of the net element dataframes and some helper functions change/may not preserve the element indices, such as `create_continuous_elements_index(net)`. As a result, by means of the indices, a group can no longer find its members.\n",
    "For that reason, groups can also detect their members by a column of the elements dataframes. That can be applied directly at the group definition or later using `set_group_reference_column()`. Then we can see, that the group does no longer store the indices but the values of the set reference column:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:12.181708Z",
     "start_time": "2025-10-20T06:20:12.144771Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"Group 1 data with indices:\\n\")\n",
    "print(net.group.loc[gr1_idx])\n",
    "\n",
    "set_group_reference_column(net, gr1_idx, \"name\")\n",
    "\n",
    "print(\"\\nAfter setting 'name' as reference column, the group stores the members by the names:\\n\")\n",
    "print(net.group.loc[gr1_idx])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see, the second group now stores the names of the members and not indices. Using `group_element_index()` you can nevertheless get the indices of the group members. You can see that these are still the same as given in the definition of the group above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:12.197151Z",
     "start_time": "2025-10-20T06:20:12.188345Z"
    }
   },
   "outputs": [],
   "source": [
    "print(group_element_index(net, gr1_idx, \"load\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Attention:** Be aware that `reference_column` only works fine if there are no duplicated values in `net[element][reference_column]` for all members of the groups!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set Values to all Group Members\n",
    "The following code block shows you how to set the value to all members of a group. A specific use case of the is to set all members in service or out of service. For that reason, these got explicit function names."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:14.529994Z",
     "start_time": "2025-10-20T06:20:12.207324Z"
    }
   },
   "outputs": [],
   "source": [
    "# setting a name value to group 2 members\n",
    "set_value_to_group(net, gr2_idx, \"member of group '%s'\" % gr2_name, \"name\")\n",
    "\n",
    "# visualize the effect\n",
    "print(\"The load names:\\n\")\n",
    "print(net.load.name)\n",
    "\n",
    "# set all elements of group 2 out of service\n",
    "set_group_out_of_service(net, gr2_idx)\n",
    "runpp(net)\n",
    "print(\"\\nThe bus results with Feeder 2 out of service:\\n\")\n",
    "print(net.res_bus)  # the Feeder 2 buses should now have nan values\n",
    "\n",
    "# and back in service...\n",
    "set_group_in_service(net, gr2_idx)\n",
    "runpp(net)\n",
    "print(\"\\nThe bus results with Feeder 2 back in service:\\n\")\n",
    "print(net.res_bus)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sum Group Consumption Power\n",
    "Groups can sum its complete power consumption including losses. Since the virtual power plant group predominantly consists of generation units, its active power value is negative."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:14.577474Z",
     "start_time": "2025-10-20T06:20:14.537212Z"
    }
   },
   "outputs": [],
   "source": [
    "for gr_idx, gr_name in zip([gr1_idx, gr2_idx], [gr1_name, gr2_name]):\n",
    "    print(\"Group '%s' consumes %.2f MW and %.2f Mvar.\" % (\n",
    "        gr_name, group_res_p_mw(net, gr_idx), group_res_q_mvar(net, gr_idx)))\n",
    "\n",
    "# a validation of Feeder 2 group values is easy since there is only one trafo and one line\n",
    "# which supply the feeder:\n",
    "p_val = net.res_line.p_to_mw.at[14] + net.res_trafo.p_hv_mw.at[1]\n",
    "assert np.isclose(group_res_p_mw(net, gr2_idx), p_val)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Append and Drop Group Members\n",
    "Once defined, it still easy to change the members of a group:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:14.608251Z",
     "start_time": "2025-10-20T06:20:14.584197Z"
    }
   },
   "outputs": [],
   "source": [
    "p_before = group_res_p_mw(net, gr1_idx)\n",
    "\n",
    "# append the virtual power plant\n",
    "attach_to_group(net, gr1_idx, [\"storage\"], [[net.storage.name.at[0]]], \"name\")\n",
    "# drop an sgen from the virtual power plant\n",
    "detach_from_group(net, gr1_idx, \"load\", [5])\n",
    "\n",
    "# validate via compare the active power consumption\n",
    "assert np.isclose(p_before + net.res_storage.p_mw.at[0] - net.res_load.p_mw.at[5], group_res_p_mw(net, gr1_idx))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Some more functionality"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For small groups, `print(net.group.loc[[group_index]]` can be used to see quickly how many members are included. For larger groups, `count_group_elements()` is available to access the number of group members per element type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:14.639764Z",
     "start_time": "2025-10-20T06:20:14.614161Z"
    }
   },
   "outputs": [],
   "source": [
    "no_member = count_group_elements(net, gr2_idx)\n",
    "print(\"Number of member per element type:\")\n",
    "print(no_member)\n",
    "print(f\"\\n Overall number of members: {no_member.sum()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If `net.group` has been corrupted, `ensure_lists_in_group_element_column()` and `remove_not_existing_group_members()` may help:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:20:14.671838Z",
     "start_time": "2025-10-20T06:20:14.646499Z"
    }
   },
   "outputs": [],
   "source": [
    "# assume net.group is corrupted for some reason, as...\n",
    "net.group.element_index.iat[-1] = 5  # no list\n",
    "net.trafo.drop(1, inplace=True)  # net elements were dropped without using toolbox functions like drop_trafos()\n",
    "\n",
    "ensure_lists_in_group_element_column(net)  # fixes no list data\n",
    "remove_not_existing_group_members(net)  # detects that transformer 1 does not exist and drops it\n",
    "print(net.group)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To explore some more functions, such as `groups_equal()`, `compare_group_elements()` and others, please have a look to the Code."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3.9.16 ('base')",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  },
  "vscode": {
   "interpreter": {
    "hash": "19d1d53a962d236aa061289c2ac16dc8e6d9648c89fe79f459ae9a3493bc67b4"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
